/*
	html-xpath, an implementation of DOM Level 3 XPath for Internet Explorer 5+
	Copyright (C) 2004 Dimitri Glazkov

	This library is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	This library is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software

*/

// Mozilla has support by default, we don't have an implementation for the rest
if (! document.evaluate)
{
    document.DomL3XPathRelease = "0.0.3.0"; // release number

    function _XPathExceptionHandler()
    {
        this.INVALID_EXPRESSION_ERR = 51;
        this.TYPE_ERR = 52;
        this.NOT_IMPLEMENTED_ERR = -1;
        this.RUNTIME_ERR = -2;

        this.ThrowNotImplemented = function(message)
        {
            ThrowError(this.NOT_IMPLEMENTED_ERR, "This functionality is not implemented.", message);
        }

        this.ThrowInvalidExpression = function(message)
        {
            ThrowError(this.INVALID_EXPRESSION_ERR, "Invalid expression", message);
        }

        this.ThrowType = function(message)
        {
            ThrowError(this.TYPE_ERR, "Type error", message);
        }

        this.Throw = function(message)
        {
            ThrowError(this.RUNTIME_ERR, "Run-time error", message);
        }

        function ThrowError(code, description, message)
        {
            var error = new Error(code, "DOM-L3-XPath " + document.DomL3XPathRelease + ": " + description + (message ? ", \"" + message  + "\"": ""));
            error.code = code;
            error.name = "XPathException";
            throw error;
        }
    }

    // XPathException
    // An Error object will be thrown, this is just a handler to instantiate that object
    var XPathException = new _XPathExceptionHandler();

    function _DOMExceptionHandler()
    {
        this.ThrowInvalidState = function(message)
        {
            ThrowError(13, "The state of the object is no longer valid", message);
        }

        function ThrowError(code, description, message)
        {
            var error = new Error(code, "DOM : " + description + (message ? ", \"" + message  + "\"": ""));
            error.code = code;
            error.name = "DOMException";
            throw error;
        }
    }

    // DOMException
    // An Error object will be thrown, this is just a handler to instantiate that object
    var DOMException = new _DOMExceptionHandler();

    // XPathEvaluator 
    // implemented as document object methods

    // XPathExpression createExpression(String expression, XPathNSResolver resolver)
    document.createExpression = function ( expression /* String */, resolver /* XPathNSResolver */)
    {
        return new XPathExpression(expression, resolver);
    }

    // XPathNSResolver createNSResolver(nodeResolver)
    document.createNSResolver = function ( nodeResolver	/* Node */)
    {
        return new XPathNSResolver(nodeResolver);
    }

    // XPathResult evaluate(String expresison, Node contextNode, XPathNSResolver resolver, Number type, XPathResult result)
    document.evaluate = function ( expression /* String */, contextNode	/* Node */, resolver /* XPathNSResolver */, type /* Number */, result /* XPathResult */)
    {
        // can raise XPathException, DOMException
        // return XPathResult
        return document.createExpression(expression, resolver).evaluate(contextNode, type, result);
    }

    // XPathExpression
    function XPathExpression ( expression /* String */, resolver /* XPathNSResolver */)
    {
        if (expression.indexOf('//') > 0)
        {
            /*
               IE can not deal with //a//span
               The first // is ok.  IE7 has problem with subsequent // 
             */
            var index = expression.indexOf('//',1);
            var firstPart = expression.substring(0, index);
            var secondPart = expression.substr(index);
            secondPart = secondPart.replace(/\/\//g,'/descendant::*/');
            expression = firstPart + secondPart;
        }
        this.expressionString = expression;
        this.resolver = resolver;

        // XPathResult evaluate(Node contextNode, Number type, XPathResult result)
        this.evaluate = function ( contextNode /* Node */, type /* Number */, result /* XPathResult */)
        {
            // can raises XPathException, DOMException
            // return XPathResult
            return (result && result.constructor == XPathResult ? result.initialize(this, contextNode, resolver, type) : new XPathResult(this, contextNode, resolver, type));
        }

        this.toString = function()
        {
            return "[XPathExpression]";
        }
    }

    // XPathNSResolver
    function XPathNSResolver(node)
    {
        this.node = node;

        // String lookupNamespaceURI(String prefix)
        this.lookupNamespaceURI = function ( prefix /* String */)
        {
            XPathException.ThrowNotImplemented();
            // return String
            return null;
        }

        this.toString = function()
        {
            return "[XPathNSResolver]";
        }
    }

    // XPathResult
    XPathResult.ANY_TYPE = 0;
    XPathResult.NUMBER_TYPE = 1;
    XPathResult.STRING_TYPE = 2;
    XPathResult.BOOLEAN_TYPE = 3;
    XPathResult.UNORDERED_NODE_ITERATOR_TYPE = 4;
    XPathResult.ORDERED_NODE_ITERATOR_TYPE = 5;
    XPathResult.UNORDERED_SNAPSHOT_TYPE = 6;
    XPathResult.ORDERED_SNAPSHOT_TYPE = 7;
    XPathResult.ANY_UNORDERED_NODE_TYPE = 8;
    XPathResult.FIRST_ORDERED_NODE_TYPE = 9;

    function XPathResult ( expression /* XPathExpression */, contextNode /* Node */, resolver /* XPathNSResolver */, type /* Number */)
    {
        this.initialize = function(expression, contextNode, resolver, type)
        {
            this._domResult = null;
            this._expression = expression;
            this._contextNode = contextNode;
            this._resolver = resolver;
            if (type)
            {
                this.resultType = type;
                this._isIterator = (type == XPathResult.UNORDERED_NODE_ITERATOR_TYPE || 
                        type == XPathResult.ORDERED_NODE_ITERATOR_TYPE || 
                        type == XPathResult.ANY_TYPE);
                this._isSnapshot = (type == XPathResult.UNORDERED_SNAPSHOT_TYPE || type == XPathResult.ORDERED_SNAPSHOT_TYPE);
                this._isNodeSet = type > XPathResult.BOOLEAN_TYPE;
            }
            else
            {
                this.resultType = XPathResult.ANY_TYPE;
                this._isIterator = true;
                this._isSnapshot = false;
                this._isNodeSet = true;
            }
            return this;
        }

        this.initialize(expression, contextNode, resolver, type);

        this.getInvalidIteratorState = function()
        {
            return documentChangeDetected() || !this._isIterator;			
        }

        this.getSnapshotLength = function()
        {
            // can raises XPathException
            if (!this._isSnapshot)
            {
                XPathException.ThrowType("Snapshot is not an expected result type");
            }
            activateResult(this);
            // return Number
            return this._domResult.length;
        }

        // Node iterateNext()
        this.iterateNext = function()
        {
            // can raises XPathException, DOMException
            if (!this._isIterator)
            {
                XPathException.ThrowType("Iterator is not an expected result type");
            }
            activateResult(this);
            if (documentChangeDetected())
            {
                DOMException.ThrowInvalidState("iterateNext");
            }
            // return Node
            return getNextNode(this);
        }

        // Node snapshotItem(Number index)
        this.snapshotItem = function(index)
        {
            // can raises XPathException
            if (!this._isSnapshot)
            {
                XPathException.ThrowType("Snapshot is not an expected result type");
            }
            // return Node
            return getItemNode(this, index); 
        }

        this.toString = function()
        {
            return "[XPathResult]";
        }

        // returns string value of the result, if result type is STRING_TYPE
        // otherwise throws an XPathException
        this.getStringValue = function()
        {
            if (this.resultType != XPathResult.STRING_TYPE)
            {
                XPathException.ThrowType("The expression can not be converted to return String");
            }
            return getNodeText(this);
        }

        // returns number value of the result, if the result is NUMBER_TYPE
        // otherwise throws an XPathException
        this.getNumberValue = function()
        {
            if (this.resultType != XPathResult.NUMBER_TYPE)
            {
                XPathException.ThrowType("The expression can not be converted to return Number");
            }
            var number = parseInt(getNodeText(this));
            if (isNaN(number))
            {
                XPathException.ThrowType("The result can not be converted to Number");
            }
            return number;
        }

        // returns boolean value of the result, if the result is BOOLEAN_TYPE
        // otherwise throws an XPathException
        this.getBooleanValue = function()
        {
            if (this.resultType != XPathResult.BOOLEAN_TYPE)
            {
                XPathException.ThrowType("The expression can not be converted to return Boolean");
            }

            var	
                text = getNodeText(this);
            bool = (text ? text.toLowerCase() : null);
            if (bool == "false" || bool == "true")
            {
                return bool;
            }
            XPathException.ThrowType("The result can not be converted to Boolean");
        }

        // returns single node, if the result is ANY_UNORDERED_NODE_TYPE or FIRST_ORDERED_NODE_TYPE
        // otherwise throws an XPathException
        this.getSingleNodeValue = function()
        {
            if (this.resultType != XPathResult.ANY_UNORDERED_NODE_TYPE && 
                    this.resultType != XPathResult.FIRST_ORDERED_NODE_TYPE)
            {
                XPathException.ThrowType("The expression can not be converted to return single Node value");
            }
            return getSingleNode(this);
        }

        function documentChangeDetected()
        {
            return document._XPathMsxmlDocumentHelper.documentChangeDetected();
        }

        function getNodeText(result)
        {
            activateResult(result);
            return result._textResult;
            // return ((node = getSingleNode(result)) ? (node.nodeType == 1 ? node.innerText : node.nodeValue) : null);
        }

        function findNode(result, current)
        {
            switch(current.nodeType)
            {
                case 1: // NODE_ELEMENT
                    var id = current.attributes.getNamedItem("id");
                    if (id)
                    {
                        return document.getElementById(id.value);
                    }
                    XPathException.Throw("unable to locate element in XML tree");
                case 2: // NODE_ATTRIBUTE
                    var id = current.selectSingleNode("..").attributes.getNamedItem("id");
                    if (id)
                    {
                        var node = document.getElementById(id.text);
                        if (node)
                        {
                            return node.attributes.getNamedItem(current.nodeName);
                        }
                    }
                    XPathException.Throw("unable to locate attribute in XML tree");
                case 3: // NODE_TEXT
                    var id = current.selectSingleNode("..").attributes.getNamedItem("id");
                    if (id)
                    {
                        var node = document.getElementById(id.value);
                        if (node)
                        {
                            for(child in node.childNodes)
                            {
                                if (child.nodeType == 3 && child.nodeValue == current.nodeValue)
                                {
                                    return child;
                                }
                            }
                        }
                    }
                    XPathException.Throw("unable to locate text in XML tree");
            }
            XPathException.Throw("unknown node type");
        }

        function activateResult(result)
        {
            if (!result._domResult)
            {
                try
                {
                    var expression = result._expression.expressionString;

                    // adjust expression if contextNode is not a document
                    if (result._contextNode != document && expression.indexOf("//") != 0)
                    {

                        expression = "//*[@id = '" + result._contextNode.id + "']" + 
                            (expression.indexOf("/") == 0 ? "" : "/") + expression;
                    }

                    if (result._isNodeSet)
                    {
                        result._domResult = document._XPathMsxmlDocumentHelper.getDom(expression).selectNodes(expression);
                    }
                    else
                    {
                        result._domResult = true;
                        result._textResult = document._XPathMsxmlDocumentHelper.getTextResult(expression);
                    }

                }
                catch(error)
                {
                    alert(error.description);
                    XPathException.ThrowInvalidExpression(error.description);
                }
            }
        }

        function getSingleNode(result)
        {
            var node = getItemNode(result, 0);
            result._domResult = null;
            return node;
        }

        function getItemNode(result, index)
        {
            activateResult(result);
            var current = result._domResult.item(index);
            return (current ? findNode(result, current) : null);
        }

        function getNextNode(result)
        {
            var current = result._domResult.nextNode;
            if (current)
            {
                return findNode(result, current);
            }
            result._domResult = null;
            return null;
        }
    }

    document.reloadDom = function()
    {
        document._XPathMsxmlDocumentHelper.reset();
    }

    document._XPathMsxmlDocumentHelper = new _XPathMsxmlDocumentHelper();
    function _XPathMsxmlDocumentHelper()
    {
        this.getDom = function(expression)
        {
            this.expression = expression;
            activateDom(this);
            return this.dom;
        }

        this.getXml = function()
        {
            activateDom(this);
            return this.dom.xml;
        }

        this.getTextResult = function(expression)
        {
            expression = expression.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "\"");
            var xslText = "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
                "<xsl:output method=\"text\"/><xsl:template match=\"*\"><xsl:value-of select=\"" + expression + "\"/>" +
                "</xsl:template></xsl:stylesheet>";
            var xsl = new ActiveXObject("Msxml2.DOMDocument");
            xsl.loadXML(xslText);
            try
            {
                var result = this.getDom().transformNode(xsl);
            }
            catch(error)
            {
                alert("Error: " + error.description);
            }
            return result;
        }

        this.reset = function()
        {
            this.dom = null;
        }

        function onPropertyChangeEventHandler()
        {
            document._propertyChangeDetected = true;
        }

        this.documentChangeDetected = function()
        {
            return (document.ignoreDocumentChanges ? false : this._currentElementCount != document.all.length || document._propertyChangeDetected);
        }

        function createDocument()
        {
            var aVersions = [
                "Msxml2.DOMDocument.6.0",
                // "Msxml2.DOMDocument.5.0",
                // "Msxml2.DOMDocument.4.0",
                "Msxml2.DOMDocument.3.0"
                    // "Msxml.DOMDocument",
                    // "Microsoft.XmlDom"
                    ];
            for (var i = 0; i < aVersions.length; i++)
            {
                try {
                    var oXmlDom = new ActiveXObject(aVersions[i]);
                    return oXmlDom;
                } catch (oError) {
                    // Do nothing
                }
            }
            throw new Error("MSXML is not initialize");
        }
        function activateDom(helper)
        {
            if (!helper.dom)
            {
                // var dom = new ActiveXObject("Msxml2.DOMDocument");
                var dom = createDocument();
                dom.async = false;
                dom.resolveExternals = false;
                dom.setProperty("SelectionLanguage","XPath");
                loadDocument(dom, helper);
                // See the innerHTML of the dom
                helper.dom = dom;
                helper._currentElementCount = document.all.length;
                document._propertyChangeDetected = false;
            }
            else
            {
                if (helper.documentChangeDetected())
                {
                    var dom = helper.dom;
                    dom.load("");
                    loadDocument(dom, helper);
                    helper._currentElementCount = document.all.length;
                    document._propertyChangeDetected = false;
                }
            }
        }

        function loadDocument(dom, helper)
        {
            loadNode(dom, dom, document.body, helper);		
        }

        function loadNode(dom, domParentNode, node, helper)
        {
            if (node.nodeType == 3)
            {			
                domParentNode.appendChild(dom.createTextNode(node.nodeValue));
            }
            else if (node.nodeType == 1)
            {
                var nodeName = node.nodeName.toLowerCase();
                if ((nodeName == "title") || (nodeName == "meta") || (nodeName == "script") || (nodeName == "style") || (nodeName == "link") || (nodeName == "head"))
                {;}
                else {
                    if (!node.id)
                    {
                        node.id = node.uniqueID;
                    }
                    if (helper.expression == '//tr[@id="individualTR"]//input[@name="contactSelectGroup"]') 
                    {
                        if (node.nodeName.search(/\//) > -1)
                        {
                            //debugger;
                        }
                    }
                    var domNode = '';
                    if (node.nodeName)
                    {
                        if (node.nodeName.search(/\//) > -1)
                        {
                            // When IE find an empty span tag as in <span ...> </span>, node.nodeName is /SPAN
                            if (node.childNodes.length == 0)
                            {
                                domNode = domParentNode;
                            }
                            else
                            {
                                // debugger;
                            }
                        }
                        else
                        {
                            domNode = dom.createElement(node.nodeName.toLowerCase());
                            domParentNode.appendChild(domNode);
                            loadAttributes(dom, domNode, node, helper);
                        }
                    }
                    else
                    {
                        domNode = domParentNode;
                    }
                    var length = node.childNodes.length;
                    for(var i = 0; i < length; i ++ )
                    {
                        loadNode(dom, domNode, node.childNodes[i], helper);
                    }
                    // node.attachEvent("onpropertychange", onPropertyChangeEventHandler);
                }
            }
        }

        function loadAttributes(dom, domParentNode, node, helper)
        {
            for (var i = 0; i < node.attributes.length; i ++ )
            {
                var attribute = node.attributes[i];
                var attributeValue = attribute.nodeValue;
                if (attributeValue && attribute.specified)
                {
                    if (typeof(attribute.nodeName) == 'textNode')
                    {
                        continue;
                    }
                    if (attribute.nodeName.search(/\//) > -1)
                    {
                        debugger;
                    }
                    var atype = typeof(attributeValue);
                    if ((atype == 'string') || (atype == 'boolean') || (atype == 'number')) { }
                    else if (atype == 'function') {
                        attributeValue = attributeValue.toString();
                    } else if (atype == 'object') {
                        continue;
                    } else {
                        debugger;
                    }
                    // helper.expression holds the original xpath
                    if (attributeValue.replace)
                    {
                        attributeValue = attributeValue.replace(/#/,'');
                    }
                    domParentNode.setAttribute(attribute.nodeName.toLowerCase(),attributeValue);
                    /*
                       var domAttribute = dom.createAttribute(attribute.nodeName);
                       domAttribute.value = attributeValue;
                       domParentNode.setAttributeNode(domAttribute);				
                     */
                }
            }
            if (node.nodeName.toLowerCase() == 'input')
            {
                // IE version of node.attributes does not contains the value attribute for input type=submit
                var valueAttr = node.getAttribute('value');
                if ((valueAttr) && (typeof(valueAttr) == 'string'))
                {
                    domParentNode.setAttribute('value',valueAttr);
                }
            }
        }

    }
}
